/* Liscense: 	GPL http://www.gnu.org/copyleft/gpl.html
 * By: 		A. Tres Finocchiaro
 * Date:	2007
 * Java:	6.0SE
 */

package com.pentajock.docdb.office;

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Date;
import javax.swing.JOptionPane;
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Variant;
import com.jacob.com.Dispatch;
import com.jacob.com.ComThread;
import com.pentajock.docdb.Main;
import com.pentajock.docdb.database.DBController;
import com.pentajock.docdb.dialogs.Dialogs;
import com.pentajock.docdb.lang.Lang;
import com.pentajock.docdb.types.DepartmentProperties;
import com.pentajock.docdb.types.Document;
import com.pentajock.docdb.types.User;
import com.pentajock.docdb.utils.Utils;
import java.sql.SQLException;

public class Jacob {
    static private String userName;

    static public void setUserName(String s) {
	    userName = s;
    }
    
    /*
     * Simple enum for deciding if JACOB can open the file
     *
     * Not sure where to store what files are supported.
     * This enum would have to be updated if more docs get supported.  Not the
     * best place, but not the worst either. :)
     */
    public enum SupportedActiveX {
    	WORD ("Word.Application"),
	EXCEL ("Excel.Application"),
	INFO ("InfoPath.Application"),
	VISIO ("Visio.Application");
	String call;
	SupportedActiveX(String call) {
		this.call = call;
	}
	
	private String getCall() {
		return call;
	}
	
	public static boolean isSupported(File f){
		for (SupportedActiveX x : SupportedActiveX.values()) {
			if (x.getCall().equalsIgnoreCase(DBController.getActiveXCall(f))) {
				return true;
			}
		}
		return false;
	}
    }
    
    

    static public ActiveXComponent initiateJacob(Object o) throws Exception {
	    File docFile;
	    if (o instanceof Document)
		    docFile = ((Document)o).toFile();
	    else if (o instanceof File)
		    docFile = (File)o;
	    else
		    throw new Exception("Unsupported Object Type");
	    
	    if (!SupportedActiveX.isSupported(docFile))
		    return null;
	    
	    String activeXCall = DBController.getActiveXCall(docFile);
	    String documentCall = DBController.getDocumentCall(docFile);
	    if (DBController.isSupportedFile(docFile) && activeXCall != null) {
		    ActiveXComponent oActiveX = new ActiveXComponent(activeXCall);
		    Main.printDebug("Using Microsoft " +activeXCall+ ": " +oActiveX.getProperty("Version")+" detected.");
		    return oActiveX;
	    }
	    
	    return null;
    }
    
    /*static public Dispatch initiateVisio(Dispatch oDocuments, File docFile, boolean readOnly) throws Exception {
	    return Dispatch.call(oDocuments, "OpenEx", docFile.getPath(), getVisioReadOnly(readOnly)).toDispatch();    
    }*/
    
    /*static public Dispatch initiateInfoPath(Dispatch oDocuments, File docFile, boolean readOnly) throws Exception  {
	    if (Utils.getExtension(docFile).equalsIgnoreCase("xsn")) {
		    return Dispatch.call(oDocuments, "NewFromSolution", docFile.getPath()).toDispatch(); //, getVisioReadOnly(readOnly)).toDispatch();
	    }
	    else {
		    return Dispatch.call(oDocuments, "Open", docFile.getPath()).toDispatch(); //, getVisioReadOnly(readOnly)).toDispatch();
	    }
    }*/
    
    /*
    static private int getVisioReadOnly(boolean readOnly) {
	    if (readOnly) return VBA.visOpenRO;
	    else return VBA.visOpenRW;
    }*/
    
    /*
     * Gets updateInterval out of document
     * If on does not exists, returns -2
     */
    static public int getUpdateInterval(Dispatch oDocument, File docFile) {
	    try {
		    if (DBController.supportsCustomProperties(docFile))
			    return Integer.parseInt(getDocumentProperty(oDocument, "CustomDocumentProperties", "Update Interval"));
	    }
	    catch (Exception e) {
		   //Main.printStackTrace(e, "Error retreiving update interval from Document");
		   Main.printDebug("(WARNING) Error retreiving update interval from Document");
	    }
	    return -2;
    }
    
    /*static public String getCategory(File docFile) {
	    try {
		    ComThread.InitSTA();
		    ActiveXComponent oActiveX = initiateJacob(docFile);
		    Dispatch oDocument = initiateDoc(oActiveX, docFile, true);
		    String category = getCategory(oDocument, docFile, false);
		    closeDocument(oActiveX, oDocument);
		    ComThread.Release();
		    return category == null || category.trim().equals("") ? null : category;
	    }
	    catch (Exception e) {
		    return null;
	    }
    }*/
    
    static public String getCategory(Dispatch oDocument, File docFile) {
	    return getCategory(oDocument, docFile, true);
    }
    
    static public String getBuiltInDocumentProperty(Dispatch oDocument, String propertyName) {
	     return getDocumentProperty(oDocument, "BuiltInDocumentProperties", propertyName);
    }
    
    static public String getCategory(Dispatch oDocument, File docFile, boolean promptIfMissing) {
	    try {
		    if (DBController.supportsCustomProperties(docFile)) {
			    //String docCategory = getDocumentProperty(oDocument, "CustomDocumentProperties", "Category");
			    String docCategory = getDocumentProperty(oDocument, "BuiltInDocumentProperties", "Category");
			    if (promptIfMissing) {
			    	if (docCategory == null) return Dialogs.categoryNameDialog(docFile);
				else if (docCategory.equals("")) return Dialogs.categoryNameDialog(docFile);
			    }
			    return docCategory;
		    }
	    }
	    catch (Exception e) {
		   //Main.printStackTrace(e, "Error retreiving category from Document");
		   Main.printDebug("(WARNING) Error retreiving category from Document");
	    }
	    return null;
    }
    
    static public Date getEffectiveDate(Dispatch oDocument, File docFile) throws Exception {
	   if (DBController.supportsCustomProperties(docFile)) {
		return Utils.parseBestDate(getDocumentProperty(oDocument, "CustomDocumentProperties", "Effective Date"), "MM/dd/yy", "MM/dd/yyyy");
	   }
	   throw new Exception("Unsupported document type " + docFile.getName());
    }
    
    /*
     * Opens the document, displaying any necessary password prompts (best for
     * standard use).
     */
    static public Dispatch initiateDoc(ActiveXComponent oActiveX, Object o, boolean readOnly) throws Exception {
	    return initiateDoc(oActiveX, o, readOnly, false);
    }
    
    /*
     * Opens the document primarily based on activeX call and documentCall.  These
     * values are stored in the database in the extensions table.  Some types,
     * such as InfoPath, changes dependant on extension, to support its default
     * template behavior.
     *
     * You'll notice some Variants such as vPassword, and vFormat.  These are
     * critical for surpassing password protected types (Most useful during
     * repopulation).  Note, the open() method supports up to 15 parameters, 
     * loosely, as VB supports "optional parameters". Jacob works with optionals
     * if they're in their correct order, hence the need for all parameters 
     * before vPassword. :\
     *
     * Currently only Word and Excel support password surpassing.
     */
    static public Dispatch initiateDoc(ActiveXComponent oActiveX, Object o, boolean readOnly, boolean skipPasswords) throws Exception {	
	    File docFile;
	    String activeXCall;
	    if (o instanceof Document) {
		    docFile = ((Document)o).toFile();
		    activeXCall = ((Document)o).getActiveXCall();
	    }
	    else if (o instanceof File) {
		    docFile = (File)o;
		    activeXCall = null;
	    }
	    else {
		    throw new Exception("Unsupported Object Type");  
	    }
	    
	    Main.printDebug("Opening document \""+ docFile.getName() +"\"");
	    String documentCall = DBController.getDocumentCall(docFile);
	    if (documentCall != null) {
		    activeXCall = (activeXCall == null? DBController.getActiveXCall(docFile) : activeXCall);
		    String extension = Utils.getExtension(docFile).toLowerCase();
		    Variant vPath = new Variant(docFile.getPath());
		    Variant vConvert = new Variant(false);
		    Variant vReadOnly = new Variant(readOnly);
		    Variant vVisReadOnly = new Variant(readOnly ? VBA.visOpenRO : VBA.visOpenRW);
		    Variant vRecent = new Variant(false);
		    Variant vPassword = new Variant("");
		    Variant vFormat = new Variant(VBA.xlAutoOpen);
		    String vInfoOpen = (extension.equals("xsn") ? "NewFromSolution" : "Open");
		    Dispatch oDocuments = oActiveX.getProperty(documentCall).toDispatch();
		
		    if (activeXCall.equalsIgnoreCase("Visio.Application")) {
			    return Dispatch.call(oDocuments, "OpenEx", vPath, vVisReadOnly).toDispatch();    
		    }
		    else if (activeXCall.equalsIgnoreCase("InfoPath.Application")) {
			    return Dispatch.call(oDocuments, vInfoOpen, docFile.getPath()).toDispatch();
		    }
		    else if (activeXCall.equalsIgnoreCase("Word.Application")) {
			    if (skipPasswords)
				    return Dispatch.call(oDocuments, "Open", vPath, vConvert, vReadOnly, vRecent, vPassword, vPassword).toDispatch();
				    //return Dispatch.call(oDocuments, "Open", vPath, vConvert, vReadOnly, vRecent, vPassword).toDispatch();
			    else
				    return Dispatch.call(oDocuments, "Open", vPath, vConvert, vReadOnly).toDispatch();
		    }
		    else if (activeXCall.equalsIgnoreCase("Excel.Application")) {
			    if (skipPasswords)
				    return Dispatch.call(oDocuments, "Open", vPath, vConvert, vReadOnly, vFormat, vPassword).toDispatch();
			    else
				    return Dispatch.call(oDocuments, "Open", vPath, vConvert, vReadOnly).toDispatch();
		    }
		    else if (activeXCall.equalsIgnoreCase("Acrobat.Application")){
			    return null;
		    }
	    }
	    throw new Exception("Document type not properly set up in database.");
    }
    
    static public void minimizeVisio(ActiveXComponent oActiveX) {
	   Dispatch.call(oActiveX, "DoCmd", VBA.visCmdAppMinimize);
    }
    
    static public int showVisioPrintDialog(ActiveXComponent oActiveX, boolean showDialog) {
	    try {
		    return Dispatch.call(oActiveX, "DoCmd", VBA.visCmdFilePrint).changeType(Variant.VariantInt).getInt();
	    }
	    catch (Exception e) {
		    //Main.printStackTrace(e, "Known Visio + JACOB Bug - Continuing.");
		    //Main.printDebug("(IGNOREME) Known Visio Print Dialog Bug - Continuing.");
		    return -1;
	    }
    }
    
    static public int showInfoPathPrintDialog(ActiveXComponent oActiveX, boolean showDialog) {
	    try {
		    Dispatch oXmlForm = Dispatch.call(oActiveX, "XmlForm").toDispatch();
		    return Dispatch.call(oXmlForm, "Print", new Variant(showDialog)).changeType(Variant.VariantInt).getInt();
	    }
	    catch (Exception e) {
		    Dialogs.showError(e, Lang.DIALOG.get("INFOPATH_PRINT_ERROR", "InfoPath 2007 or higher required for this action"));
		    return -1;
	    }
    }
    
    static public boolean docVisible(ActiveXComponent oActiveX, boolean v) {
	    Main.printDebug("Setting Visible=" + v + "... &");
	    try {
		    Dispatch.put(oActiveX, "Visible", new Variant(v));
		    Main.printDebug("successful");
	    }
	    catch (Exception e) {
		    Main.printDebug("failed");
	    }
	    Main.printDebug("Setting ScreenUpdating=" + v + "... &");
	    try {
		    Dispatch.put(oActiveX, "ScreenUpdating", new Variant(v));
		    Main.printDebug("successful");
		    return true;
	    }
	    catch (Exception e) {
		    Main.printDebug("failed");
		    return false;
	    }
    }
    
    static private void printDocument(Object o, final boolean dialogVisible) {
	    final File docFile;
	    if (o instanceof Document)
		    docFile = ((Document)o).toFile();
	    else if (o instanceof File)
		    docFile = (File)o;
	    else
		    return;
		
	    new Thread(new Runnable() {
		public void run() {
			if (System.getProperty("os.name").toLowerCase().startsWith("windows")) {
				ComThread.InitSTA();
				ActiveXComponent oActiveX = null;
				Dispatch oDocument = null;
				try {
					oActiveX = initiateJacob(docFile);
					oDocument = initiateDoc(oActiveX, docFile, true);
					showPrintDialog(oActiveX, dialogVisible, docFile);
					closeDocument(oActiveX, oDocument);
					return;
				}
				catch (Exception e) {
					//Main.printStackTrace(e, "Jacob print error. Trying natively.");
					Main.printDebug("(WARNING) Jacob print error. Trying natively.");
					//Dialogs.showError(e, Lang.DIALOG.get("DOCUMENT_OPEN_ERROR", "I/O Error opening document"));
				}
				closeDocument(oActiveX, oDocument);
				ComThread.Release();
			}
			try {
				Utils.desktopCall("print", docFile);
				Dialogs.showSuccess(docFile, Lang.DIALOG.get("PRINT_SUCCESS", "Document sent to your default printer"));
			}
			catch (Exception e) {
				Dialogs.showError(e, Lang.DIALOG.get("PRINT_SUPPORT_ERROR", "Printing functionality not working"));
			}
		}
	    }).start();
    }
		
    static public void printDocument(Object o) {
		printDocument(o, true);
    }
    
    static private int getPrintDialogInteger(File docFile) {
	    String appCall = DBController.getActiveXCall(docFile);
	    if (appCall.equalsIgnoreCase("Word.Application")) return VBA.wdDialogFilePrint;
	    else if (appCall.equalsIgnoreCase("Excel.Application")) return VBA.xlDialogPrint;
	    return 0;
    }
    
    static private int showPrintDialog(ActiveXComponent oActiveX, boolean showDialog, File docFile) throws Exception {
	    	int dialogResult = 0;
		docVisible(oActiveX, false);
		if (DBController.getActiveXCall(docFile).equalsIgnoreCase("Visio.Application")) {
			dialogResult = showVisioPrintDialog(oActiveX, showDialog);
		}
		else if (DBController.getActiveXCall(docFile).equalsIgnoreCase("InfoPath.Application")) {
			dialogResult = showInfoPathPrintDialog(oActiveX, showDialog);
		}
		else {
			Main.printDebug("Printing document... &");
			int printDialog = getPrintDialogInteger(docFile);
			if (showDialog) dialogResult = Dispatch.call(Dispatch.call(Dispatch.call(oActiveX, "Dialogs").toDispatch(), 0, printDialog).toDispatch(), "Show").changeType(Variant.VariantInt).getInt();
			else dialogResult = Dispatch.call(Dispatch.call(Dispatch.call(oActiveX, "Dialogs").toDispatch(), 0, printDialog).toDispatch(), "Execute").changeType(Variant.VariantInt).getInt();
		}
		docVisible(oActiveX, false); // Because the print dialog makes it visible again
		return dialogResult;
    }
	
    public static ActiveXComponent openDocument(boolean readOnly, Object o, boolean isVisible) {
		File docFile;
		if (o instanceof File)
			docFile = (File)o;
		else if (o instanceof Document)
			docFile = ((Document)o).toFile();
		else if (o instanceof String)
			docFile = new File((String)o);
		else
			return null;
	    
	    	ComThread.InitSTA();
	    	ActiveXComponent oActiveX = null;
		Dispatch oDocument = null;
	    	try {
			oActiveX = initiateJacob(docFile);
			if (oActiveX != null) {
				oDocument = initiateDoc(oActiveX, docFile, readOnly);
				if (oDocument != null) {
					docVisible(oActiveX, false);
					if (!readOnly) {
						String[] d = Utils.getConstants(oDocument, oActiveX, docFile, DBController.getUser(userName));
						Dispatch oTable = headerFooterAutomation(oDocument, oActiveX, docFile, d);
						fillProps(d, oDocument, oTable, docFile);
					}
					docVisible(oActiveX, isVisible);
				}
			}
			ComThread.Release();
			return oActiveX;
		 }
		 catch (Exception e) {
			Dialogs.showError(e, o, Lang.DIALOG.get("DOCUMENT_OPEN_ERROR", "I/O Error opening document"));
			closeDocument(oActiveX, oDocument);
			ComThread.Release();
			/* Open with native application, but return null, since we can't automate */
			try {
				Utils.desktopCall("open", docFile);
			}
			catch (Exception x) {}
			return null;
		 }
    }
    
    
    
    private static Dispatch headerFooterAutomation(Dispatch oDocument, ActiveXComponent oActiveX, File docFile, String[] d) {
		Dispatch oTable = null;
	    	if (!DBController.supportsHeader(docFile)) return null;
	    	String docName = docFile.getName();
	    	Main.printDebug("Starting Header / Footer automation...");
		Dispatch oSelection = focusToHeader(oDocument, oActiveX);
		if (oSelection != null) {
			int x = 7; int y = 3;	// Table dimensions: 'x' cells wide, 'y' cells tall
			oTable = insertTable(oDocument, oSelection, x, y);
			sizeCells(oTable);
			decorateCells(oTable, new int[] {3,7});		// Border style for colums 3 and 7
			insertFooter(oDocument, oSelection, oActiveX);
			focusToBody(oDocument, oActiveX);
			moveToBeginning(oActiveX);
		}
		return oTable;
    }
    
    static private boolean moveToBeginning(Dispatch oActiveX) {
	    try {
		    /* Scrambles the views and a positive side-effect is focusing back to top of document :p */ 
		    Dispatch oActiveWindow = Dispatch.call(oActiveX, "ActiveWindow").toDispatch();
		    Dispatch oView = Dispatch.call(oActiveWindow, "View").toDispatch(); 
		    int viewType = Dispatch.get(oView, "Type").changeType(Variant.VariantInt).getInt();
		    Dispatch.put(oView, "Type", VBA.wdOutlineView);
		    Dispatch.put(oView, "Type", viewType);
		    return true;
	    }
	    catch (Exception e) {
		    //Main.printStackTrace(e, "Error focusing to beginning of document");
		    Main.printDebug("(WARNING) Error focusing to beginning of document");
	    }
	    return false;
    }
    
    static public boolean insertFooter(Dispatch oDocument, Dispatch oSelection, ActiveXComponent oWord) {
	    	try {
			focusToFooter(oDocument, oWord);
			Dispatch.put(Dispatch.call(oSelection, "Font").toDispatch(), "Size", 8);
			Dispatch oRange = Dispatch.call(Dispatch.call(oSelection, "HeaderFooter").toDispatch(), "Range").toDispatch();
			Dispatch.put(oRange, "Text", "");
			Dispatch oTable = Dispatch.call(Dispatch.get(oDocument, "Tables").toDispatch(), "Add", oRange, 1, 2 ).toDispatch();
			Dispatch.put(oTable, "PreferredWidthType", VBA.wdPreferredWidthPercent);
			Dispatch.put(oTable, "PreferredWidth", "100");
			Dispatch.put(Dispatch.call(oTable, "Columns", 2).toDispatch(), "PreferredWidth", 40);
			Dispatch oNormalTemplate = Dispatch.call(oWord, "NormalTemplate").toDispatch();
			Dispatch oFileNameAndPath = Dispatch.call(oNormalTemplate, "AutoTextEntries", "Filename and path").toDispatch();
			Dispatch.call(oFileNameAndPath, "Insert", Dispatch.call(oSelection, "Range").toDispatch(), new Variant(false));
			Dispatch.call(oSelection, "MoveRight", VBA.wdCell);
			Dispatch oCellRange = Dispatch.call(Dispatch.call(oTable, "Cell", 1,2).toDispatch(), "Range").toDispatch();
			Dispatch.put(Dispatch.call(oCellRange, "ParagraphFormat").toDispatch(), "Alignment", VBA.wdAlignParagraphRight);
			oRange = Dispatch.call(oSelection, "Range").toDispatch();
			Dispatch oPageXofY = Dispatch.call(oNormalTemplate, "AutoTextEntries", "Page X of Y").toDispatch();
			Dispatch.call(oPageXofY, "Insert", oRange, new Variant(false));
			return true;
		}
		catch (Exception e) {
			//Main.printStackTrace(e, "Change focus to document footer was unsuccessful.");
			Main.printDebug("(WARNING) Change focus to document footer was unsuccessful.");
			return false;
		}  
    }
    
    static public boolean closeDocument(ActiveXComponent oActiveX, Dispatch oDocument) {
	    boolean docClosed = closeDocument(oDocument) && closeApplication(oActiveX);
	    Main.printDebug("Closing document " + (docClosed ? "succeeded." : "failed."));
	    return docClosed;
    }
    
    static public boolean closeDocument(Dispatch oDocument) {
	     if (oDocument != null) {
		    try {
			    Dispatch.call(oDocument, "Close", new Variant(false));
			    return true;
		    }
		    catch (Exception e) {}
	    }
	    oDocument = null;
	    return false;
    }
    
    static public boolean closeApplication(ActiveXComponent oActiveX) {
	    if (oActiveX != null) {
		    try {
			  Dispatch.call(oActiveX, "Quit", new Variant(false));
			  return true;
		    }
		    catch (Exception e) {}
		    try {
			  Dispatch.call(oActiveX, "Quit");
			  return true;
		    }
		    catch (Exception e) {}
	    }
	    oActiveX = null;
	    return false;
    }
    
    static public Dispatch focusToHeader(Dispatch oDocument, ActiveXComponent oWord) {
		return focusTo(oDocument, oWord, VBA.wdSeekCurrentPageHeader);
    }
    
    static public Dispatch focusToFooter(Dispatch oDocument, ActiveXComponent oWord) {
		return focusTo(oDocument, oWord, VBA.wdSeekCurrentPageFooter);
    }
    
    static public Dispatch focusToBody(Dispatch oDocument, ActiveXComponent oWord) {
		return focusTo(oDocument, oWord, VBA.wdSeekMainDocument);
    }
    
    static public Dispatch focusTo(Dispatch oDocument, ActiveXComponent oWord, int wdSeek) {
	    try {
			Dispatch oActiveWindow = Dispatch.call(oDocument, "ActiveWindow").toDispatch();
			Dispatch oActivePane = Dispatch.call(oActiveWindow, "ActivePane").toDispatch();
			Dispatch oView = Dispatch.call(oActivePane, "View").toDispatch();
			Dispatch.put(oView, "SeekView", wdSeek);
			Dispatch oSelection = oWord.getProperty("Selection").toDispatch();
			return oSelection;
	    }
	    catch (Exception e) {
		    	//Main.printStackTrace(e, "Error setting focus to document. wdSeek:=" + wdSeek);
		    	Main.printDebug("(WARNING) Error setting focus to document. wdSeek:=" + wdSeek);
			return null;
	    }
    }
    
    /* Insert a table x by y cells into the selection */
    static public Dispatch insertTable(Dispatch oDocument, Dispatch oSelection, int x, int y) {
		try {
			/** Call the header/footer and clear it. It will only clear a cell if we don't */
			Dispatch oRange = Dispatch.call(Dispatch.call(oSelection, "HeaderFooter").toDispatch(), "Range").toDispatch();
			Dispatch.put(oRange, "Text", "");
			Main.printDebug("Focusing on document header...");
			Dispatch.call(Dispatch.call(oDocument, "Sections", 1).toDispatch(), "Headers", 1).toDispatch();
			Main.printDebug("Inserting table...");
			Dispatch oTables = Dispatch.get(oDocument, "Tables").toDispatch();
			Dispatch.put(Dispatch.call(oSelection, "Font").toDispatch(), "Size", 10);
			return(Dispatch.call(oTables, "Add", oRange, y, x ).toDispatch());
		}
		catch (Exception e) { 
			//Main.printStackTrace(e, "Error inserting table into header.\n(con't) This could be caused by prematurely closing the document.");
			Main.printDebug("(WARNING) Error inserting table into header.\n(con't) This could be caused by prematurely closing the document.");
			return null;
		}
    }
    
    static public boolean fillProps(String[] d, Dispatch oDocument, Dispatch oTable, File docFile) {
	    	/* Fills Header Table, BuiltInProperties, CustomProperties with property values */
	    	boolean supportsProperties = DBController.supportsBuiltInProperties(docFile);
	    	boolean supportsCustom = DBController.supportsCustomProperties(docFile);
		boolean supportsHeader = DBController.supportsHeader(docFile);
		boolean visioFix = DBController.getActiveXCall(docFile).equalsIgnoreCase("Visio.Application");
	    	String department = DBController.getUserDepartment(userName);
		
		if (supportsCustom) clearCustomProperties(oDocument);
		try  {
			
			DepartmentProperties.PropertyID PROPERTY_NAME = DepartmentProperties.PropertyID.PROPERTY_NAME;
			DepartmentProperties.PropertyID POSITION_IN_HEADER = DepartmentProperties.PropertyID.POSITION_IN_HEADER;
			DepartmentProperties.PropertyID STATIC_VALUE = DepartmentProperties.PropertyID.STATIC_VALUE;
			DepartmentProperties.PropertyID SHOW_IN_PROPERTIES = DepartmentProperties.PropertyID.SHOW_IN_PROPERTIES; 
			DepartmentProperties propertiesFull =  DBController.getDepartmentPropertiesFull(department);
			DepartmentProperties.Property singleProperty;
			String propertyName;
			
			for (String s : DBController.getDepartmentProperties(department)) {
				singleProperty =  propertiesFull.getProperty(s);
				propertyName = (String)singleProperty.get(PROPERTY_NAME);
				String propertyValue = (String)singleProperty.get(STATIC_VALUE);
				int positionInHeader = (Integer)singleProperty.get(POSITION_IN_HEADER);
				int userIndex = Utils.checkUserConstants(propertyName);
				boolean showInProperties = (Boolean)singleProperty.get(SHOW_IN_PROPERTIES);
				boolean showInHeader = (positionInHeader > -1);
				if (userIndex != -1) {
					propertyValue = d[userIndex];
				}
				if (visioFix) insertVisioProperty(oDocument, propertyName, propertyValue);
				else {
					if (supportsCustom && showInProperties) insertCustomProperty(oDocument, propertyName, propertyValue);
					if (supportsProperties) insertBuiltInProperty(oDocument, propertyName, propertyValue);
					if (supportsHeader && showInHeader) insertHeaderProperty(oTable, propertyName, propertyValue, positionInHeader);
				}
			}
			return true;
		}
		catch (SQLException sql) {
			Main.printStackTrace(sql, "Error retreiving properties from database");
			return false;
		}
		
    }
    
    static private boolean insertHeaderProperty(Dispatch oTable, String propertyName, String propertyValue, int q) {
	    try {	
		int y = q / 2 + 1;	// Determines column
		int x = q % 2 * 4 + 1;	// Determines row
		Dispatch oCellRange = Dispatch.call(Dispatch.call(oTable, "Cell", y,x).toDispatch(), "Range").toDispatch();
		Dispatch.put(Dispatch.call(oCellRange, "ParagraphFormat").toDispatch(), "Alignment", VBA.wdAlignParagraphRight);
		Dispatch.put(oCellRange, "Text", propertyName + ":");
		oCellRange = Dispatch.call(Dispatch.call(oTable, "Cell", y, x + 2).toDispatch(), "Range").toDispatch();
		Dispatch.put(Dispatch.call(oCellRange, "ParagraphFormat").toDispatch(), "Alignment", VBA.wdAlignParagraphLeft);
		Dispatch.put(oCellRange, "Text", propertyValue);
		return true;
	    }
	    catch (Exception e) {
		    //Main.printStackTrace(e, "Error inserting header value: " + propertyValue);
		    Main.printDebug("(WARNING) Error inserting header value: " + propertyValue);
	    }
	    return false;
    }
  
    static private boolean clearCustomProperties(Dispatch oDocument) {
	    	/* Clear out all custom doc properties */
		Dispatch oDocProp = null;
		try {
			oDocProp = Dispatch.get(oDocument, "CustomDocumentProperties").toDispatch();
		}
		catch (Exception e) {
			//Main.printStackTrace(e, "Error accessing CustomDocumentProperties for Delete.");
			Main.printDebug("(WARNING) Error accessing CustomDocumentProperties for Delete.");
			return false;
		}
		try {
			if (oDocProp == null) return false; 
			int propCount = Dispatch.get(oDocProp, "Count").changeType(Variant.VariantInt).getInt();
			for (int i = 0 ; i < propCount; i++) Dispatch.call(Dispatch.call(oDocProp, "Item", 1).toDispatch(), "Delete");
		}
		catch (Exception e) {
			//Main.printStackTrace(e, "Error deleting a custom property value.");
			Main.printDebug("(WARNING) Error deleting a custom property value.");
			return false;
		}
		return true;
    }
    
    static private boolean isBuiltInProperty(String s) {
	    String builtInProperties = "Manager, Company, Category, Subject, Title";
	    for (String t : builtInProperties.split(",")) {
		    if (s.equalsIgnoreCase(t.trim())) return true;
	    }
	    return false;
    }
    
    static private boolean insertCustomProperty(Dispatch oDocument, String propertyName, String propertyValue) {
	    try {
		   Dispatch oDocProp = Dispatch.get(oDocument, "CustomDocumentProperties").toDispatch();
		   if (propertyName.equals("Author")) propertyName = "Owner";	// To stay consistant w/ old program
		   Dispatch.call(oDocProp, "Add", propertyName, new Variant(false), VBA.msoPropertyTypeString, propertyValue);
		   return true; 
	    }
	    catch (Exception e) {
		   //Main.printStackTrace(e, "Error inserting a CustomDocumentProperties value.");
		   Main.printDebug("(WARNING) Error inserting a CustomDocumentProperties value.");
	    }
	    return false;
    }
    
    static private boolean insertBuiltInProperty(Dispatch oDocument, String propertyName, String propertyValue) {
	    if (isBuiltInProperty(propertyName)) {
		    try {
			    Dispatch oDocProp = Dispatch.get(oDocument, "BuiltInDocumentProperties").toDispatch();
			    Dispatch oCat = Dispatch.call(oDocProp, "Item", propertyName).toDispatch();
			    Dispatch.put(oCat, 0, propertyValue);	//Not sure why it needs a zero, but it seems to work
			    if (propertyName.equalsIgnoreCase("Subject")) Dispatch.put(Dispatch.call(oDocProp, "Item", "Title").toDispatch(), 0, propertyValue);
			    return true;
		    }
		    catch (Exception e) {
			    //Main.printStackTrace(e, "Error inserting a BuiltInDocumentProperties value.");
			    Main.printDebug("(WARNING) Error inserting a BuiltInDocumentProperties value.");
		    }
	    }
	    return false;
    }
    
    static private boolean insertVisioProperty(Dispatch oDocument, String propertyName, String propertyValue) {
	    if (isBuiltInProperty(propertyName)) {
		    try {
			    if (propertyName.equalsIgnoreCase("Author")) propertyName = "Creator"; //Necessary Visio fix
			    if (propertyName.equalsIgnoreCase("Subject")) Dispatch.put(oDocument, "Title", propertyValue);
			    Dispatch.put(oDocument, propertyName, propertyValue);
			    return true;
		    }
		    catch (Exception e) {
			    Main.printDebug("(WARNING) Error inserting a Visio BuildInDocumentProperties value.");
			    //Main.printStackTrace(e, "Error inserting a Visio BuiltInDocumentProperties value.");
		    }
	    }
	    return false;
    }
    
   
    /* 
     * Size cell widths, table offset, etc 
     */
    static public boolean sizeCells(Dispatch oTable) {
		Main.printDebug("Setting up table / cell size...");
		try {	
			Main.printDebug("Sizing: &");
			int x = getColumnCount(oTable);
			int y = getRowCount(oTable);
			
			Main.printDebug("Indent... &");
			Dispatch oRow = Dispatch.call(oTable, "Rows").toDispatch();
			for (String[] i : DBController.getTableDimensions("LEFT", "JACOB")) {
				if (i[0].equals("1")) Dispatch.call(oRow, "SetLeftIndent", Integer.parseInt(i[1]), VBA.wdAdjustNone); break;
			}
			
			
			Main.printDebug("Width... &");
			for (String[] i : DBController.getTableDimensions("WIDTH", "JACOB")) {
				if (i[0].equals("1")) Dispatch.put(oTable, "PreferredWidth", i[1]); break;
			}
			
			
			Main.printDebug("Columns... &");
			for (String[] i : DBController.getTableDimensions("COL", "JACOB")) {
				//Main.printDebug(i[0] + ":" + i[1] + ", &");
				Dispatch oColumn = Dispatch.call(oTable, "Columns", i[0]).toDispatch();
				Dispatch.put(oColumn, "PreferredWidth", i[1]);
			}
			
			Main.printDebug("Complete.");
			return true;
		}
		catch (Exception e) {
			//Main.printStackTrace(e, "Error sizing rows / colums in header table.");
			Main.printDebug("(WARNING) Error sizing rows / columns in header table.");
		}
		return false;
    }
    
    /* 
     * Borders / Decorative lines: 
     */
    static public boolean decorateCells(Dispatch oTable, int[] columns) {
		try {
			Main.printDebug("Decorating cells... &");
			for (int i = 0; i < columns.length; i++) {
				Dispatch oColumn = Dispatch.call(oTable, "Columns", columns[i]).toDispatch();
				Dispatch oBorders = Dispatch.call(oColumn, "Borders", VBA.wdBorderBottom).toDispatch();
				Dispatch.put(oBorders, "LineStyle", VBA.wdLineStyleSingle);
				oBorders = Dispatch.call(oColumn, "Borders", VBA.wdBorderHorizontal).toDispatch();
				Dispatch.put(oBorders, "LineStyle", VBA.wdLineStyleSingle);
				
			}
			Main.printDebug("Complete.");
			return true;
		}
		catch (Exception e) {
			//Main.printStackTrace(e, "Error decorating cells.");
			Main.printDebug("(WARNING) Error decorating cells.");
			return false;
		}
    }
    
     /* 
      * Look through the word doc's properties for the author 
      */
    static private String findAuthorInProperties(Dispatch oDocument, File docFile) {
	    if (DBController.supportsBuiltInProperties(docFile)) {
		    // Visio fix:
		    if (DBController.getActiveXCall(docFile).equalsIgnoreCase("Visio.Application")) {
			    return getVisioProperty(oDocument, "Creator");
		    }
		    return getDocumentProperty(oDocument, "BuiltInDocumentProperties", "Author");
	    }
	    else return null;
    }
    
    static private String findAuthorInCustomProperties(Dispatch oDocument, File docFile) {
	    	if (DBController.supportsCustomProperties(docFile)) return getDocumentProperty(oDocument, "CustomDocumentProperties", "Owner");
		else return null;
    }
    
    static private String getDocumentProperty(Dispatch oDocument, String propertyHandle, String propertyName) {
	    	try {   
			String value = Dispatch.call(Dispatch.get(oDocument, propertyHandle).toDispatch(), "Item", propertyName).toString().trim();
			Main.printDebug("Found '" + propertyName + "' \"" + value + "\" in " + propertyHandle + ".");
			return value;
		}		
		catch (Exception e) {
			//Main.printStackTrace(e, "Could not find '" + propertyName + "' in " + propertyHandle);
			Main.printDebug("(WARNING) Could not find '" + propertyName + "' in " + propertyHandle);
			return null;
		}
    }
    
    static public String getCustomDocumentProperty(Dispatch oDocument, String propertyName) {
	    return getDocumentProperty(oDocument, "CustomDocumentProperties", propertyName);
    }
    
    static private String getVisioProperty(Dispatch oDocument, String propertyName) {
	    	try {   
			String value = Dispatch.get(oDocument, propertyName).toString().trim();
			Main.printDebug("Found '" + propertyName + "' \"" + value + "\" in Visio properties");
			return value;
		}		
		catch (Exception e) {
			//Main.printStackTrace(e, "Could not find '" + propertyName + "' in Visio properties");
			Main.printDebug("(WARNING) Could not find '" + propertyName + "' in Visio properties");
			return null;
		}
    }
    
    static public String getAuthor(Dispatch oDocument, File docFile, User userAccount) {
	    	String a1 = findAuthorInProperties(oDocument, docFile);
		String a2 = findAuthorInCustomProperties(oDocument, docFile);
	    
		a2 = (a2 == null || a2.trim().equals("") ? userAccount.getFullName() : a2); 
		return (a1 == null || a1.trim().equals("") ? a2 : a1);
    }
    
    static private int getRowCount(Dispatch oTable) {
	    try {
		    return oTable.get(Dispatch.call(oTable, "Rows").toDispatch(), "Count").changeType(Variant.VariantInt).getInt();
	    }
	    catch (Exception e) {
		    //Main.printStackTrace(e, "Error getting row count");
		    Main.printDebug("(WARNING) Error getting row count");
		    return 0;
	    }
    }
    
    static private int getColumnCount(Dispatch oTable) {
	    try {
		    return oTable.get(Dispatch.call(oTable, "Columns").toDispatch(), "Count").changeType(Variant.VariantInt).getInt();
	    }
	    catch (Exception e) {
		    //Main.printStackTrace(e, "Error getting column count");
		    Main.printDebug("(WARNING) Error getting column count");
		    return 0;
	    }
    }
    
    /* **SAVE** DISPATCH CALLS:
     *	Dispatch.put(Dispatch.call(oActiveX, "Options").toDispatch(), 
     *		"PrintBackground", new Variant(false));  // May be needed for Word -- Dont delete
     */
}
